package ml.practicum.learn;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import ml.practicum.logistic.BasicLV;
/**
 * Implements the bare minimum of the Perceptron interface
 * 
 * Using an logistic activation function, from an innerclass
 * Can be initialized by the number of weights (size of the perceptron)
 * And optionally an activation function
 * 
 * Does everything a perceptron needs to do,
 * <ul>
 *  <li>set activation function</li>
 *  <li>Store weights and bias</li>
 *  <li>randomizes/initializes</li>
 *  <li>calculate output according to inputs </li>
 * </ul>
 *  
 * @author Joscha
 *
 * @param <T>
 */
public class BasicPerceptron<T extends Number> implements Perceptron<T>, Serializable {	/**
	 * 
	 */
	private static final long serialVersionUID = -7727055433838631912L;
	private double bias;
	private List<Double> weights;
	private ActivationFunction<T> activationFunction;
	
	/**
	 * Constructor using the number of weights needed by the perceptron
	 * @param noWeights number of weights to determine the size of the perceptron
	 */
	public BasicPerceptron(int noWeights) {
		this(noWeights, 
				new ActivationFunction<T>(){
					/**
					 * 
					 */
					private static final long serialVersionUID = 3677454281758203777L;

			@SuppressWarnings("unchecked")
			public T activation(double input){
				double logistic = 1./(1. + Math.pow(Math.E, -input));
				return (T) new BasicLV(logistic);
			}
		}
		);
	}
	/**
	 * Constructor to set the number of weights and set an activation function
	 * @param noWeights number of weights to determine the size of the perceptron
	 * @param activationFunction activation function for the perceptron to use in calculation
	 */
	public BasicPerceptron(int noWeights, ActivationFunction<T> activationFunction) {
		super();
		this.activationFunction = activationFunction;
		randomize(-1,1, noWeights);
	}

	public double getBias() {
		return bias;
	}

	public void setBias(double bias) {
		this.bias = bias;
	}

	public List<Double> getWeights() {
		return weights;
	}


	public void setWeights(List<Double> weights) {
		this.weights = weights;
	}


	public ActivationFunction<T> getActivationFunction() {
		return activationFunction;
	}


	public void setActivationFunction(ActivationFunction<T> activationFunction) {
		this.activationFunction = activationFunction;
	}

	public void randomize(double min, double max, int nrWeights) {
		this.weights = new ArrayList<Double>(nrWeights);
		setBias(generateWeight(min,max));
		for(int i = 0; i<nrWeights; i++){	
			this.weights.add(generateWeight(min,max));
		}
	}
	
	/**
	 * Generates a random value between the minimum and maximum set value
	 * @param min minimum allowed weight
	 * @param max maximum allowed weight
	 * @return the weigth that is generated
	 */
	private double generateWeight(double min, double max){
		return (Math.random()*Math.abs(max-min))+min;
	}

	public T calculate(List<T> input) {
		double sum = bias;
		for(int i = 0; i<weights.size();i++){
			sum += weights.get(i) * input.get(i).doubleValue();
		}
		return activationFunction.activation(sum);
	}

}
